package com.gitee.wsl.mathematics.geometry.d3.polygon

import com.gitee.wsl.ext.collection.ArrayIdMap
import com.gitee.wsl.ext.collection.IdMap
import com.gitee.wsl.ext.collection.associateById
import com.gitee.wsl.ext.collection.avgOf
import com.gitee.wsl.ext.collection.distinctIndexed
import com.gitee.wsl.ext.list.swap
import com.gitee.wsl.mathematics.base.IsoDir
import com.gitee.wsl.mathematics.geometry.d3.edge.Edge
import com.gitee.wsl.mathematics.geometry.d3.edge.EdgeEquivalenceClass
import com.gitee.wsl.mathematics.geometry.d3.edge.EdgeKind
import com.gitee.wsl.mathematics.geometry.d3.edge.IsoEdge
import com.gitee.wsl.mathematics.geometry.d3.edge.normalizedDirection
import com.gitee.wsl.mathematics.geometry.d3.ext.MidPoint
import com.gitee.wsl.mathematics.geometry.d3.ext.isTangentInSegment
import com.gitee.wsl.mathematics.geometry.d3.ext.midPoint
import com.gitee.wsl.mathematics.geometry.d3.face.Face
import com.gitee.wsl.mathematics.geometry.d3.face.FaceKind
import com.gitee.wsl.mathematics.geometry.d3.face.FaceKindSource
import com.gitee.wsl.mathematics.geometry.d3.face.FaceRim
import com.gitee.wsl.mathematics.geometry.d3.face.MutableFace
import com.gitee.wsl.mathematics.geometry.d3.face.getVertex
import com.gitee.wsl.mathematics.geometry.d3.face.size
import com.gitee.wsl.mathematics.geometry.d3.polygon.ext.Scale
import com.gitee.wsl.mathematics.geometry.d3.polygon.transform.DropExpansion
import com.gitee.wsl.mathematics.geometry.d3.vertex.AnyKind
import com.gitee.wsl.mathematics.geometry.d3.vertex.MutableVertex
import com.gitee.wsl.mathematics.geometry.d3.vertex.Vertex
import com.gitee.wsl.mathematics.geometry.d3.vertex.VertexKind
import kotlinx.serialization.Serializable


@Serializable(with = PolyhedronSerializer::class)
class Polyhedron(
    vs: List<MutableVertex>,
    fs: List<MutableFace>,
    val faceKindSources: List<FaceKindSource>? // non-null when polyhedron was transformed
)  {
    val vs: List<Vertex> = vs
    val fs: List<Face> = fs
    val es: List<Edge>
    val directedEdges: List<Edge>

    // `build edges (unidirectional & directed) and link them with vertices and faces
    init {
        val es = ArrayList<Edge>()
        val directedEdges = ArrayList<Edge>()
        val lFaces = ArrayIdMap<Vertex, HashMap<Vertex, MutableFace>>()
        for (f in fs) {
            for (i in 0 until f.size) {
                val a = f.getVertex(i)
                val b = f.getVertex((i + 1) % f.size)
                require(a.id != b.id) { "Duplicate vertices at face $f" }
                if (a.id <= b.id) continue
                lFaces.getOrPut(b) { HashMap() }[a] = f
            }
        }
        for (rf in fs) {
            for (i in 0 until rf.size) {
                val a = rf.fvs[i]
                val b = rf.fvs[(i + 1) % rf.size]
                if (a.id >= b.id) continue
                val lf = lFaces[a]?.get(b)
                require(lf != null) {
                    "Edge $a to $b on face $rf does not have an adjacent face"
                }
                val ea = Edge(a, b, lf, rf)
                val eb = Edge(b, a, rf, lf)
                ea.reversed = eb
                eb.reversed = ea
                es += ea.normalizedDirection()
                directedEdges += ea
                directedEdges += eb
                a.directedEdges.add(ea)
                b.directedEdges.add(eb)
                rf.directedEdges.add(ea)
                lf.directedEdges.add(eb)
            }
        }
        for (v in vs) v.directedEdges.sortVertexAdjacentEdges(v)
        for (f in fs) f.directedEdges.sortFaceAdjacentEdges(f)
        this.es = es
        this.directedEdges = directedEdges
    }

    val vertexKinds: IdMap<VertexKind, Vertex> by lazy { vs.associateById({ it.kind }, { it }) }
    val faceKinds: IdMap<FaceKind, Face> by lazy { fs.associateById({ it.kind }, { it }) }
    val edgeKinds: Map<EdgeKind, Edge> by lazy { es.associateBy({ it.kind }, { it }) }
    val directedEdgeKinds: Map<EdgeKind, Edge> by lazy { directedEdges.associateBy({ it.kind }, { it }) }

    val vertexKindCount: IdMap<VertexKind, Int> by lazy { vs.groupingBy { it.kind }.eachCountTo(ArrayIdMap()) }
    val faceKindCount: IdMap<FaceKind, Int> by lazy { fs.groupingBy { it.kind }.eachCountTo(ArrayIdMap()) }
    val edgeKindCount: Map<EdgeKind, Int> by lazy { es.groupingBy { it.kind }.eachCount() }

    val nonPlanarFaceKinds by lazy {
        faceKinds.entries
            .filter { (_, f0) -> !f0.isPlanar }
            .map { (fk, _) -> fk }
    }

    val edgeKindsIndex: Map<EdgeKind, Int> by lazy {
        es.asSequence()
            .map { it.kind }
            .distinctIndexed { it }
    }

    val directedEdgeKindsIndex: Map<EdgeKind, Int> by lazy {
        directedEdges.asSequence()
            .map { it.kind }
            .distinctIndexed { it }
    }

    val isoEdges: List<IsoEdge> by lazy {
        val ke = directedEdges.associateBy({ it.kind }, { it })
        val ki = ke.entries.associateBy(
            keySelector = { (ek, _) -> ek },
            valueTransform = { (ek, e) ->
                IsoEdge(ek, EdgeEquivalenceClass(e))
            }
        )
        for (ie in ki.values) {
            ie.lNext = ki[ke[ie.kind]!!.next(IsoDir.L).kind]!!
            ie.rNext = ki[ke[ie.kind]!!.next(IsoDir.R).kind]!!
        }
        ki.values.toList()
    }

    val inradius: Double by lazy { fs.minOf { f -> f.d } }
    val midradius: Double by lazy { es.avgOf { e -> e.midPoint(MidPoint.Closest).length } }
    val circumradius: Double by lazy { vs.maxOf { v -> v.length } }

    fun scaleDenominator(scale: Scale): Double = when(scale) {
        Scale.Inradius -> inradius
        Scale.Midradius -> midradius
        Scale.Circumradius -> circumradius
    }

    // Radius that is used for for polar reciprocation to compute dual,
    // the key requirement is that dual points of regular polygon's faces must be in the centers of those faces
    val dualReciprocationRadius: Double
        get() = inradius

    val edgesMidPointDefault: MidPoint by lazy {
        if (es.all { e -> e.isTangentInSegment() }) MidPoint.Tangent else MidPoint.Center
    }

    private val faceRims by lazy { ArrayIdMap<Face, FaceRim>() }

    fun faceRim(f: Face) =
        faceRims.getOrPut(f) { FaceRim(f) }

    val canDrop: Set<AnyKind> by lazy {
        val kinds = mutableSetOf<AnyKind>()
        kinds += vertexKinds.keys
        kinds += faceKinds.keys
        kinds += edgeKinds.keys
        kinds.filter {
            DropExpansion(this, it).isValid
        }.toSet()
    }

    override fun toString(): String =
        "Polyhedron(vs=${vs.size}, es=${es.size}, fs=${fs.size})"
}

private fun MutableList<Edge>.sortVertexAdjacentEdges(v: Vertex) {
    require(all { it.a == v })
    for (i in 1 until size) {
        val prev = this[i - 1].r
        val j = (i until size).first { this[it].l == prev }
        swap(i, j)
    }
}

private fun MutableList<Edge>.sortFaceAdjacentEdges(f: Face) {
    require(all { it.r == f })
    for (i in 1 until size) {
        val prev = this[i - 1].b
        val j = (i until size).first { this[it].a == prev }
        swap(i, j)
    }
}